﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using UnitLibrary;
using PhysicsObjects;
using UnitLibrary.GameObjects;
using Microsoft.Xna.Framework.Graphics;
using UnitLibrary.Utility;
using UnitLibrary.GameObjects.Units;
using UnitLibrary.GameObjects.UnitReaders;
using System.IO;

namespace UnitLibrary.Managers
{
    /// <summary>
    /// Extremely robust and generic class to handle all the basic functions for every UnitManager.
    /// This class is to be inherited by the unit manager, x manager. It'll do the hard labor for you.
    /// Not that the UnitType must use interface IGameObject. The IGameObject is a contract forcing all unittype to have the same methods as declared in it. This will allow the unitmanager to "blindly" but safely make calls to those methods.
    /// </summary>
    /// <typeparam name="UnitType"></typeparam>
    public abstract class UnitM<UnitType>: Manager, IManager where UnitType : IGameObject, new()
    {
        public UnitM(Game game, ComponentM componentManager, UnitID containerType)
            : base(game, componentManager)
        {
            _objectDefinitons = new Dictionary<string, UnitType>();
            _unitNode1 = new RPool<UnitType>.Node(-1, UnitID.Border);
            _unitNode2 = new RPool<UnitType>.Node(-1, UnitID.Border);
            _unitManagerData = new UnitManagerData();
            _containerType = containerType;
        }

        protected bool PreLoadIntoPhysics = false;

        protected bool RemoveRefFromPhysics = false;

        /// <summary>
        /// Container type for manager
        /// </summary>
        protected UnitID _containerType;

        /// <summary>
        /// The max objects for that manager
        /// </summary>
        protected int _maxObjects = 300;

        /// <summary>
        /// Dictionary to hold all the game object definitions for that manager
        /// </summary>       
        protected Dictionary<string, UnitType> _objectDefinitons;        

        /// <summary>
        /// Resource pool to hold all the units managed
        /// </summary>
        protected RPool<UnitType> _unitPool;

        protected RPool<UnitType>.Node _unitNode1;
        protected RPool<UnitType>.Node _unitNode2;

        protected UnitManagerData _unitManagerData;

        protected string _unitTypeName = typeof(UnitType).Name.ToString();
        /// <summary>
        /// Publicly exposed unit pool for conveince
        /// </summary>
        public RPool<UnitType> UnitPool
        {
            get { return _unitPool; }
        }

        /// <summary>
        /// Get a temporary unit node to work with
        /// </summary>
        public RPool<UnitType>.Node UnitNode1
        {
            get { return _unitNode1; }
            set { _unitNode1 = value; }
        }

        /// <summary>
        /// Get a temporary unit node to work with
        /// </summary>
        public RPool<UnitType>.Node UnitNode2
        {
            get { return _unitNode2; }
            set { _unitNode2 = value; }
        }

        public string UnitTypeName
        {
            get { return _unitTypeName; }
        }

        public virtual void LoadContent()
        {
            _unitPool = new RPool<UnitType>(_maxObjects, _containerType);  
            
            //Will use when more than one type of unit is needed
            //Iterate through a list of different unit definitions
            foreach (string UnitName in _unitManagerData.ListOfUnits)
            {
                try
                {
                    if (!ObjectSerialization<UnitType>.DoesAssetExist(UnitName + ".xml"))
                        throw new FileNotFoundException("File based on asset name doesn't exist. Check the list of unit names, and check the file names to make sure they match.");
                    //Declare and create a reference to a unit definition
                    UnitType unitDefinition = new UnitType();
                    //Declare and create a reference to a unit from xml
                    UnitType unitToCopyFrom = ObjectSerialization<UnitType>.Deserialize(UnitName);
                    //Load the unit using the given name
                    unitDefinition.Create(unitToCopyFrom, _components, Vector2.Zero, Vector2.Zero, false);
                    if (unitDefinition == null)
                        throw new NullReferenceException("Unit didn't preload correctly, still null.");
                    //Add the unit to the dictionary
                    _objectDefinitons.Add(UnitName, unitDefinition);
                }
                catch (FileNotFoundException notfound)
                {
                    Console.WriteLine("Trying to load asset {0} from xml in {1}Manager failed! Because:", UnitName, _unitTypeName);
                    Console.WriteLine(notfound.Message);
                    Console.WriteLine(notfound.StackTrace);
                    Console.WriteLine();
                }
                catch (NullReferenceException notloaded)
                {
                    Console.WriteLine("Trying to load asset {0} from xml in {1}Manager failed! Because:", UnitName, _unitTypeName);
                    Console.WriteLine(notloaded.Message);
                    Console.WriteLine(notloaded.StackTrace);
                    Console.WriteLine();
                }
            }
            
            UnitType unit= ObjectSerialization<UnitType>.Deserialize("Default");
            //Loop to iterate through all the objects in the unit pool and make sure that the encapsulated objects inside the objects in the unit pool are created
            foreach(RPool<UnitType>.Node newObject in _unitPool.AllNodes)
            {
                //Set values for the object for the gameplay
                newObject.Item.Create(unit, _components, Vector2.Zero, Vector2.Zero, PreLoadIntoPhysics);
            }
        }
        /// <summary>
        /// Update every active unit in the unit pool.
        /// </summary>
        /// <param name="gameTime"></param>
        public virtual void Update(GameTime gameTime)
        {
            //Update the unit pool, will delete units
            _unitPool.Update();
            //Iterate through all the active units
            foreach (RPool<UnitType>.Node unit in _unitPool.ActiveNodes)
                //Have the active unit update itself
                unit.Item.Update(gameTime);
            //Have the base class update
        }

        /// <summary>
        /// Function to draw every unit in the object pools active node enumeration.
        /// <remarks>This function will automatically be called as it is a part of this class being a xna game component</remarks>
        /// </summary>
        /// <param name="gameTime"></param>
        public virtual void Draw(SpriteBatch spriteBatch)
        {
            //Iterate through all the active units in the unit pool
            foreach (RPool<UnitType>.Node unit in _unitPool.ActiveNodes)
            {
                //Draw the active units
                unit.Item.Draw(spriteBatch);
            }
        }

        /// <summary>
        /// Remove a unit from the game. Use the node id from the object to remove it from this unit manager.
        /// </summary>
        /// <param name="nodeID">The Unique ID the unit has for this unit manager.</param>
        public virtual void Remove(Iid id)
        {
            if (_containerType != id.UnitID)
                throw new NotSupportedException("Wrong type of unitID");
            int nodeID = id.NodeID;
            //Have the unit remove itself from the game
            _unitPool.Get(nodeID).Item.Remove(RemoveRefFromPhysics);
            //Have the unit pool mark it as out of the game
            _unitPool.Remove(nodeID);
        }

        public virtual UnitType GetDefinition(string assetName)
        {
            try
            {
                if (_objectDefinitons.Count == 0 && _objectDefinitons == null)
                    throw new NullReferenceException("The " + _unitTypeName + " definitions dictionary in " + typeof(UnitM<UnitType>).Name + " contains no entries, or was not instantiated. \n");
                //Make sure the key is in the dictionary
                if (_objectDefinitons.ContainsKey(assetName))
                {
                    //Declare a unit Definiton
                    UnitType unitDefinition;
                    //If it is, then retrieve
                    //Retrieve an existing instance of the unit
                    _objectDefinitons.TryGetValue(assetName, out unitDefinition); 
                    if (unitDefinition == null)
                        throw new NullReferenceException(assetName + " was not instantiated before being put into the unit definitions. \n");
                    return unitDefinition;
                }
                else
                {
                    throw new NullReferenceException("Error during the loading of a " + _unitTypeName + "! It was not pre-loaded! \n Will try to load asset name " + assetName);
                }
            }
            catch (NullReferenceException e)
            {
                Console.WriteLine(e.Message);
                try
                {
                    if (ObjectSerialization<UnitType>.DoesAssetExist(assetName + ".xml"))
                    {
                        //Declare and create a reference to a unit definition
                        UnitType unitDefinition = new UnitType();

                        UnitType unitToCopyFrom = ObjectSerialization<UnitType>.Deserialize(assetName);
                        //Load the unit using the given name
                        unitDefinition.Create(unitToCopyFrom, _components, Vector2.Zero, Vector2.Zero, false);
                        //Add the unit to the dictionary
                        _objectDefinitons.Add(assetName, unitDefinition);
                        Console.WriteLine("Succesfully loaded " + _unitTypeName + " " + assetName + "\n");
                        //Try over again
                        return GetDefinition(assetName);
                    }
                    else
                    {
                        throw new FileNotFoundException("Could not find file which defines " + _unitTypeName + " unit name: " + assetName);
                    }
                }
                catch (FileNotFoundException load)
                {
                    Console.WriteLine("Could not load " + _unitTypeName + " because: ");
                    Console.WriteLine(load.Message);
                    Console.WriteLine("Check in directory: " + ObjectSerialization<UnitType>.GetLocation(assetName) + " and make sure it's there!");
                    Console.WriteLine();
                    return default(UnitType);
                }
            }
        }

        /// <summary>
        /// "Create" or bring into play a given unit object.
        /// </summary>
        /// <param name="assetName">The name of the unit object to load from the content pipeline</param>
        /// <param name="location">The location to start the object at</param>
        public virtual void Create(string assetName, Vector2 location, Vector2 direction)
        { 
            try
            {
                if (_objectDefinitons.Count == 0 && _objectDefinitons == null)
                        throw new NullReferenceException("The " + _unitTypeName + " definitions dictionary in " + typeof(UnitM<UnitType>).Name + " contains no entries, or was not instantiated. \n");
                //Make sure the key is in the dictionary
                if (_objectDefinitons.ContainsKey(assetName))
                {
                    //Declare a unit Definiton
                    UnitType unitDefiniton;
                    //Declare a unit Value
                    UnitType unitToCreate;
                    //If it is, then retrieve
                    //Retrieve an existing instance of the unit
                    _objectDefinitons.TryGetValue(assetName, out unitDefiniton);
                    if (unitDefiniton == null)
                        throw new NullReferenceException(assetName + " was not instantiated before being put into the unit definitions. \n");
                    if (_unitPool == null)
                        throw new NullReferenceException("Can not add unit to unit pool because the unit pool is not instantiated. \n");
                    //Retrieve a unit from the pool
                    unitToCreate = _unitPool.Retrieve().Item;
                    if (unitToCreate == null)
                        throw new NullReferenceException("The unit retrieved from the unit pool was not instantiated during loading. \n");
                    //Have the unit that was just created do a deep copy of values from the unit definition
                    unitToCreate.Create(unitDefiniton, _components, location, direction, true);
                    //The unit we just created is marked as in the game and now has the proper values to be in the game
                }
                else
                {
                    throw new NullReferenceException("Error during the loading of a " + _unitTypeName + "! It was not pre-loaded! \n Will try to load asset name " + assetName);
                }
            }
            catch (NullReferenceException e)
            {
                Console.WriteLine(e.Message);
                try
                {
                    if (ObjectSerialization<UnitType>.DoesAssetExist(assetName + ".xml"))
                    {
                        //Declare and create a reference to a unit definition
                        UnitType unitDefinition = new UnitType();

                        UnitType unitToCopyFrom = ObjectSerialization<UnitType>.Deserialize(assetName);
                        bool AddToPhysics = false;
                        if (RemoveRefFromPhysics)
                            AddToPhysics = true;
                        //Load the unit using the given name
                        unitDefinition.Create(unitToCopyFrom, _components, location, direction, AddToPhysics);
                        //Add the unit to the dictionary
                        _objectDefinitons.Add(assetName, unitDefinition);
                        //Try over again
                        this.Create(assetName, location, direction);
                        Console.WriteLine("Succesfully loaded " + _unitTypeName + " " + assetName + "\n");
                    }
                    else
                        throw new FileNotFoundException("Could not find file which defines " + _unitTypeName + " unit name: " + assetName);
                }
                catch (FileNotFoundException load)
                {
                    Console.WriteLine("Could not load " + _unitTypeName + " because: ");
                    Console.WriteLine(load.Message);
                    Console.WriteLine("Check in directory: " + ObjectSerialization<UnitType>.GetLocation(assetName) + " and make sure it's there!");
                    Console.WriteLine();
                }
                catch (ArgumentException badargument)
                {
                    Console.WriteLine(badargument.Message);
                    Console.WriteLine(badargument.Source);
                }
            }  
        }

        /// <summary>
        /// Function to remove the last used unit
        /// </summary>
        public void RemoveLastUnit()
        {
            //Dequeue the first node of the checked out linked list.
            //Nodes are added to the very back, so this is the oldest node.
            int nodeIndex = _unitPool.CheckedOut.First.Value;
            //Have it remove itself
            //_unitPool.Get(nodeIndex).Item.Remove();
            //Remove from the unitpool
            _unitPool.Remove(nodeIndex);
        }

        public Vector2 GetUnitPosition(int nodeID)
        {
            Vector2 unitPosition = Vector2.Zero;
            unitPosition = _unitPool.Get(nodeID).Item.Position;
            return unitPosition;
        }
    }
}